在昨天的 [Day 25] 中,我們利用 Cloud Tasks + Cloud Pub/Sub 打通了長影音背景非同步提煉與 Webhook 通知管線,將伺服器響應延遲極速降至 85ms。
當使用者在 OmniVibe AI 平台上累積了數十甚至數百筆影片、Podcast 音訊與 PDF 文檔後,單一檔案的提煉與問答已經無法滿足商業情境。使用者常會提出跨檔案的綜合性疑問,例如:
今天,我們將引進 Qdrant 向量資料庫 + Google Gemini Embedding API,實作一套具備元資料過濾(Payload Filtering)與高準確度的 Multi-Modal Hybrid RAG(跨資產混合檢索系統),讓 OmniVibe AI 從「單檔提煉工具」升級為「企業級個人知識大腦」!
跨資產檢索的核心挑戰在於:必須在海量切片(Chunks)中,既要保持語意相關性(Dense Vector),又要精確鎖定特定的使用者資產屬性(Payload Metadata),最後由 Gemini 1.5 Pro 進行多來源的資料歸納與引文標註(Source Citations)。
graph TD
subgraph 1. 向量切片建庫 (Ingestion Pipeline)
Asset[媒體資產 (PDF / 影音逐字稿)] --> Chunking[語意切片 Semantic Chunking]
Chunking --> EmbedModel[Gemini text-embedding-004]
EmbedModel --> DenseVector[768維 稠密向量]
DenseVector --> QdrantUpsert[Qdrant 寫入 Point + Payload]
end
subgraph 2. 跨資產混合檢索 (Hybrid Retrieval)
UserQuery[跨資產提問 Query] --> QEmbed[Gemini Embedding]
QEmbed --> QdrantSearch[Qdrant 向量搜尋 + Payload Filter (userId)]
QdrantSearch --> TopK[篩選 Top-K 最相關 Context Chunks]
end
subgraph 3. 歸納與引文生成 (Synthesis & Citation)
TopK --> SynthesisPrompt[構築帶有來源標記的 RAG Prompt]
SynthesisPrompt --> GeminiPro[Gemini 1.5 Pro 大腦]
GeminiPro --> FinalResponse[回傳精準解答 + 附帶來源影片時間軸/頁碼]
end
src/lib/rag/ingest.ts)我們在處理完檔案後,將內容依據語意進行 Chunk 切片,使用 Gemini text-embedding-004 生成向量,並將檔案名稱、時間軸標記(Timestamp)與使用者 ID 寫入 Qdrant Payload:
// src/lib/rag/ingest.ts
import { QdrantClient } from '@qdrant/js-client-rest';
import { GoogleGenerativeAI } from '@google/generative-ai';
const qdrant = new QdrantClient({
url: process.env.QDRANT_URL || 'http://localhost:6333',
apiKey: process.env.QDRANT_API_KEY,
});
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
const COLLECTION_NAME = 'omnivibe_knowledge_rag';
export interface AssetChunk {
chunkId: string;
assetId: string;
userId: string;
fileName: string;
mimeType: string;
text: string;
timestampRange?: string; // 例如 "05:20 - 07:15" 或 "Page 12"
}
/**
* 取得文本的 768 維 Embedding 向量
*/
async function generateEmbedding(text: string): Promise<number[]> {
const model = genAI.getGenerativeModel({ model: 'text-embedding-004' });
const result = await model.embedContent(text);
return result.embedding.values;
}
/**
* 將資產切片大量寫入 Qdrant 向量資料庫
*/
export async function ingestAssetChunks(chunks: AssetChunk[]) {
console.log(`[RAG Ingestion] 開始處理解析 ${chunks.length} 個資產切片...`);
const points = await Promise.all(
chunks.map(async (chunk) => {
const vector = await generateEmbedding(chunk.text);
return {
id: chunk.chunkId,
vector,
payload: {
assetId: chunk.assetId,
userId: chunk.userId,
fileName: chunk.fileName,
mimeType: chunk.mimeType,
text: chunk.text,
timestampRange: chunk.timestampRange || 'N/A',
createdAt: new Date().toISOString(),
},
};
})
);
// 寫入 Qdrant Collection
await qdrant.upsert(COLLECTION_NAME, {
wait: true,
points,
});
console.log(`[RAG Ingestion] 成功將 ${points.length} 個 Point 入庫至 Qdrant!`);
}
src/lib/rag/retrieval.ts)為了避免檢索到其他租戶的敏感資料,我們利用 Qdrant 的 Payload Filtering,強制限定在當前 userId 的資產集合中進行語意相干度比對:
// src/lib/rag/retrieval.ts
import { QdrantClient } from '@qdrant/js-client-rest';
import { GoogleGenerativeAI } from '@google/generative-ai';
const qdrant = new QdrantClient({
url: process.env.QDRANT_URL || 'http://localhost:6333',
apiKey: process.env.QDRANT_API_KEY,
});
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
const COLLECTION_NAME = 'omnivibe_knowledge_rag';
export interface RetrievedContext {
fileName: string;
timestampRange: string;
text: string;
score: number;
}
/**
* 進行跨資產向量混合檢索
*/
export async function retrieveRelevantContexts(
userId: string,
userQuery: string,
topK: number = 5
): Promise<RetrievedContext[]> {
// 1. 將使用者的查詢問題轉為向量
const embeddingModel = genAI.getGenerativeModel({ model: 'text-embedding-004' });
const queryEmbedding = await embeddingModel.embedContent(userQuery);
// 2. 於 Qdrant 進行向量比對,並透過 Payload 嚴格限制只能檢索該 userId 的資料
const searchResults = await qdrant.search(COLLECTION_NAME, {
vector: queryEmbedding.embedding.values,
filter: {
must: [
{ key: 'userId', match: { value: userId } }, // 租戶安全隔離
],
},
limit: topK,
score_threshold: 0.65, // 相關性品質門檻
});
return searchResults.map((hit) => ({
fileName: hit.payload?.fileName as string,
timestampRange: hit.payload?.timestampRange as string,
text: hit.payload?.text as string,
score: hit.score,
}));
}
src/app/api/ai/cross-asset-rag/route.ts)最後,我們建立跨資產 RAG 整合 API 端點。將檢索出的片段加上來源 metadata 餵給 Gemini 1.5 Pro,指示其回答問題並明確標註出處:
// src/app/api/ai/cross-asset-rag/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { retrieveRelevantContexts } from '@/lib/rag/retrieval';
import { GoogleGenerativeAI } from '@google/generative-ai';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
export async function POST(req: NextRequest) {
try {
const { userId, query } = await req.json();
if (!userId || !query) {
return NextResponse.json({ error: '缺少必要的 userId 或 query' }, { status: 400 });
}
// 1. 自 Qdrant 檢索最相關的 Top-5 知識片段
const contexts = await retrieveRelevantContexts(userId, query, 5);
if (contexts.length === 0) {
return NextResponse.json({
answer: '在您的個人資產庫中未找到與該問題相關的資料,請嘗試上傳更多相關影音或文檔。',
sources: [],
});
}
// 2. 格式化 Context 片段並標註來源
const formattedContext = contexts
.map(
(c, idx) =>
`[來源 ${idx + 1}]: 檔案【${c.fileName}】 (時間/位置: ${c.timestampRange})\n內容: ${c.text}`
)
.join('\n\n---\n\n');
// 3. 呼叫 Gemini 1.5 Pro 進行資料綜合歸納與引文標註
const model = genAI.getGenerativeModel({
model: 'gemini-1.5-pro',
systemInstruction: `
你是一位精準的跨資產知識助理。請完全根據提供的 Context 資料來回答使用者的問題。
【回答規範】:
1. 你的回答必須精確、客觀,嚴禁捏造 Context 中未提及的事實。
2. 在引用特定觀點或數據時,請務必在句末以 [來源 X: 檔案名稱 (時間/位置)] 標註出處來源。
3. 若 Context 中的資料不足以完整回答問題,請誠實說明。
`,
});
const prompt = `
以下是從您的個人資產庫中檢索出的相關知識片段:
${formattedContext}
使用者問題:${query}
請提供條理清晰的回答並標明資料引文出處:
`;
const result = await model.generateContent(prompt);
const answerText = result.response.text();
return NextResponse.json({
answer: answerText,
sources: contexts.map((c) => ({
fileName: c.fileName,
timestampRange: c.timestampRange,
relevanceScore: (c.score * 100).toFixed(1) + '%',
})),
});
} catch (error: any) {
console.error('[Cross Asset RAG Error]:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
我們在上傳了 3 部 Podcast 影音(音訊)與 2 份 PDF 白皮書後,發起跨資產提問:
提問:「在我上傳的檔案中,專家們對於 2026 年 HBM4 記憶體的技術突破有什麼共識與不同看法?」
根據您個人資產庫中的記錄,專家們對 2026 年 HBM4 技術的看法如下:
1. 共識部分:
- 記憶體頻寬將突破 1.5 TB/s,全面成為解決大模型推論 Memory-Wall 的核心 [來源 1: semiconductor_report_2026.pdf (Page 14)]。
- 基礎底板 (Base Die) 將全面轉向 12nm/5nm 先進邏輯製程打造 [來源 2: tech_podcast_ep42.mp3 (12:35 - 14:10)]。
2. 觀點差異:
- 關於熱耗散 (Thermal Management) 的解決方案,PDF 報告傾向採用浸沒式液冷技術 [來源 1: semiconductor_report_2026.pdf (Page 18)];而 Podcast 中的專家則認為傳統微通道散熱仍能維持至 2027 年 [來源 2: tech_podcast_ep42.mp3 (18:50 - 20:15)]。
今天我們成功實現了 Multi-Modal Hybrid RAG 跨資產知識檢索系統:
text-embedding-004** 與 Qdrant 向量資料庫,建構具備多租戶安全隔離的知識檢索庫。有了強大的提煉與跨檔案知識庫檢索能力後,如果 AI 還能主動幫我們「採取行動(Take Action)」——例如自動生成 Google Slides 簡報、寄送摘要 Email、或是自動建立 Google 行事曆行程,產品價值將迎來爆炸性成長。
👉 明天(Day 27),我們將進入【Agentic AI 工具調用篇】:實戰 Gemini 1.5 Function Calling 與 Agent 代理機制!看我們如何讓 AI 自動為你操作外部 API 與自動化工作流!
我們明天見!🔥